﻿using System;
using System.Collections.Generic;
using System.Net.Sockets;
using JetBlack.IO;

namespace JetBlack.Net.Sockets
{
    internal class NonBlockingWriter
    {
        public readonly Socket Socket;
        private readonly Queue<byte[]> queue = new Queue<byte[]>();
        private byte[] buffer = null;
        private byte[] len = null;
        private int offset = 0;

        public NonBlockingWriter(Socket socket)
        {
            Socket = socket;
        }

        public void Push(byte[] bytes)
        {
            lock (this)
            {
                if (buffer == null)
                {
                    buffer = bytes;
                    len = NetworkBitConverter.GetBytes(buffer.Length);
                }
                else
                    queue.Enqueue(bytes);
            }
        }

        public bool IsEmpty
        {
            get { lock (this) { return buffer == null; } }
        }

        public bool Write()
        {
            lock (this)
            {
                if (len != null)
                {
                    offset += Write(Socket, len, offset);

                    if (offset == len.Length)
                    {
                        offset = 0;
                        len = null;
                    }
                }

                if (buffer != null)
                {
                    offset += Write(Socket, buffer, offset);

                    if (offset == buffer.Length)
                    {
                        offset = 0;
                        if (queue.Count == 0)
                            buffer = null;
                        else
                        {
                            buffer = queue.Dequeue();
                            len = NetworkBitConverter.GetBytes(buffer.Length);
                        }
                    }
                }

                return buffer == null;
            }
        }

        static private int Write(Socket socket, byte[] buf, int offset)
        {
            SocketError errorCode;
            int bytesWritten = socket.Send(buf, offset, buf.Length - offset, SocketFlags.Partial, out errorCode);

            if (!IsValidWrite(errorCode, bytesWritten))
                throw new System.IO.EndOfStreamException();

            return bytesWritten;
        }

        static private bool IsValidWrite(SocketError errorCode, int bytesWritten)
        {
            return
                errorCode == SocketError.WouldBlock ||
                (errorCode == SocketError.Success && bytesWritten > 0);
        }

    }
}
